/**
 * @file	
 * @author	chipsea
 * @brief	
 * @version	0.1
 * @date	2020-11-30
 * @copyright Copyright (c) 2020, CHIPSEA Co., Ltd.
 * @note
 */
/*********************************************************************
 * INCLUDES
 */
#include "sdk_config.h"
#include "bcomdef.h"
#include "OSAL.h"
#include "osal_cbTimer.h"
#include "osal_snv.h"
#include "hci_tl.h"
#include "l2cap.h"
#include "linkdb.h"
#include "gap.h"
#include "gapbondmgr.h"
#include "central.h"

/*********************************************************************
 * MACROS
 */

/*********************************************************************
 * CONSTANTS
 */

// Profile Events
#define START_ADVERTISING_EVT         0x0001
#define RSSI_READ_EVT                 0x0002
#define UPDATE_PARAMS_TIMEOUT_EVT     0x0004

// Profile OSAL Message IDs
#define GAPCENTRALROLE_RSSI_MSG_EVT   0xE0

/*********************************************************************
 * TYPEDEFS
 */

// RSSI read data structure
typedef struct
{
  uint16        period;
  uint16        connHandle;
  uint8         timerId;
} gapCentralRoleRssi_t;

// OSAL event structure for RSSI timer events
typedef struct
{
  osal_event_hdr_t      hdr;
  gapCentralRoleRssi_t  *pRssi;
} gapCentralRoleRssiEvent_t;

/*********************************************************************
 * GLOBAL VARIABLES
 */

/*********************************************************************
 * EXTERNAL VARIABLES
 */

/*********************************************************************
 * EXTERNAL FUNCTIONS
 */

/*********************************************************************
 * LOCAL VARIABLES
 */

// Task ID
static uint8 gapCentralRoleTaskId;

// App callbacks
static gapCentralRoleCB_t *pGapCentralRoleCB;

// Array of RSSI read structures
static gapCentralRoleRssi_t gapCentralRoleRssi[GAPCENTRALROLE_NUM_RSSI_LINKS];

/*********************************************************************
 * Profile Parameters - reference GAPCENTRALROLE_PROFILE_PARAMETERS for
 * descriptions
 */

static uint8  gapCentralRoleIRK[KEYLEN];
static uint8  gapCentralRoleSRK[KEYLEN];
static uint32 gapCentralRoleSignCounter;
static uint8  gapCentralRoleBdAddr[B_ADDR_LEN];
static uint8  gapCentralRoleMaxScanRes = 0;

/*********************************************************************
 * LOCAL FUNCTIONS
 */
static void gapCentralRole_ProcessOSALMsg( osal_event_hdr_t *pMsg );
static void gapCentralRole_ProcessGAPMsg( gapEventHdr_t *pMsg );
static gapCentralRoleRssi_t *gapCentralRole_RssiAlloc( uint16 connHandle );
static gapCentralRoleRssi_t *gapCentralRole_RssiFind( uint16 connHandle );
static void gapCentralRole_RssiFree( uint16 connHandle );
static void gapCentralRole_timerCB( uint8 *pData );

/*********************************************************************
 * PUBLIC FUNCTIONS
 */

/**
 * @brief   Start the device in Central role.  This function is typically
 *          called once during system startup.
 *
 * Public function defined in central.h.
 */
bStatus_t GAPCentralRole_StartDevice( gapCentralRoleCB_t *pAppCallbacks )
{
  if ( pAppCallbacks )
  {
    pGapCentralRoleCB = pAppCallbacks;
  }

  return GAP_DeviceInit( gapCentralRoleTaskId, GAP_PROFILE_CENTRAL,
                         gapCentralRoleMaxScanRes, gapCentralRoleIRK,
                         gapCentralRoleSRK, &gapCentralRoleSignCounter );
}

/**
 * @brief   Set a parameter in the Central Profile.
 *
 * Public function defined in central.h.
 */
bStatus_t GAPCentralRole_SetParameter( uint16 param, uint8 len, void *pValue )
{
  bStatus_t ret = SUCCESS;

  switch ( param )
  {
    case GAPCENTRALROLE_IRK:
      if ( len == KEYLEN )
      {
        VOID osal_memcpy( gapCentralRoleIRK, pValue, KEYLEN ) ;
      }
      else
      {
        ret = bleInvalidRange;
      }
      break;

    case GAPCENTRALROLE_SRK:
      if ( len == KEYLEN )
      {
        VOID osal_memcpy( gapCentralRoleSRK, pValue, KEYLEN ) ;
      }
      else
      {
        ret = bleInvalidRange;
      }
      break;

    case GAPCENTRALROLE_SIGNCOUNTER:
      if ( len == sizeof ( uint32 ) )
      {
        gapCentralRoleSignCounter = *((uint32*)pValue);
      }
      else
      {
        ret = bleInvalidRange;
      }
      break;

    case GAPCENTRALROLE_MAX_SCAN_RES:
      if ( len == sizeof ( uint8 ) )
      {
        gapCentralRoleMaxScanRes = *((uint8*)pValue);
      }
      else
      {
        ret = bleInvalidRange;
      }
      break;

    default:
      ret = INVALIDPARAMETER;
      break;
  }

  return ret;
}

/**
 * @brief   Get a parameter in the Central Profile.
 *
 * Public function defined in central.h.
 */
bStatus_t GAPCentralRole_GetParameter( uint16 param, void *pValue )
{
  bStatus_t ret = SUCCESS;

  switch ( param )
  {
    case GAPCENTRALROLE_IRK:
      VOID osal_memcpy( pValue, gapCentralRoleIRK, KEYLEN ) ;
      break;

    case GAPCENTRALROLE_SRK:
      VOID osal_memcpy( pValue, gapCentralRoleSRK, KEYLEN ) ;
      break;

    case GAPCENTRALROLE_SIGNCOUNTER:
      *((uint32*)pValue) = gapCentralRoleSignCounter;
      break;

    case GAPCENTRALROLE_BD_ADDR:
      VOID osal_memcpy( pValue, gapCentralRoleBdAddr, B_ADDR_LEN ) ;
      break;

    case GAPCENTRALROLE_MAX_SCAN_RES:
      *((uint8*)pValue) = gapCentralRoleMaxScanRes;
      break;

    default:
      ret = INVALIDPARAMETER;
      break;
  }

  return ret;
}

/**
 * @brief   Terminate a link.
 *
 * Public function defined in central.h.
 */
bStatus_t GAPCentralRole_TerminateLink( uint16 connHandle )
{
  return GAP_TerminateLinkReq( gapCentralRoleTaskId, connHandle, HCI_DISCONNECT_REMOTE_USER_TERM ) ;
}

/**
 * @brief   Establish a link to a peer device.
 *
 * Public function defined in central.h.
 */
bStatus_t GAPCentralRole_EstablishLink( uint8 highDutyCycle, uint8 whiteList,
                                        uint8 addrTypePeer, uint8 *peerAddr )
{
  gapEstLinkReq_t params;

  params.taskID = gapCentralRoleTaskId;
  params.highDutyCycle = highDutyCycle;
  params.whiteList = whiteList;
  params.addrTypePeer = addrTypePeer;
  VOID osal_memcpy( params.peerAddr, peerAddr, B_ADDR_LEN );

  return GAP_EstablishLinkReq( &params );
}

/**
 * @brief   Update the link connection parameters.
 *
 * Public function defined in central.h.
 */
bStatus_t GAPCentralRole_UpdateLink( uint16 connHandle, uint16 connIntervalMin,
                                     uint16 connIntervalMax, uint16 connLatency,
                                     uint16 connTimeout )
{
  return (bStatus_t) HCI_LE_ConnUpdateCmd( connHandle, connIntervalMin,
                                            connIntervalMax, connLatency,
                                            connTimeout, 0, 0 );
}

/**
 * @brief   Start a device discovery scan.
 *
 * Public function defined in central.h.
 */
bStatus_t GAPCentralRole_StartDiscovery( uint8 mode, uint8 activeScan, uint8 whiteList )
{
  gapDevDiscReq_t params;

  params.taskID = gapCentralRoleTaskId;
  params.mode = mode;
  params.activeScan = activeScan;
  params.whiteList = whiteList;

  return GAP_DeviceDiscoveryRequest( &params );
}

/**
 * @brief   Cancel a device discovery scan.
 *
 * Public function defined in central.h.
 */
bStatus_t GAPCentralRole_CancelDiscovery( void )
{
  return GAP_DeviceDiscoveryCancel( gapCentralRoleTaskId );
}

/**
 * @brief   Start periodic RSSI reads on a link.
 *
 * Public function defined in central.h.
 */
bStatus_t GAPCentralRole_StartRssi( uint16 connHandle, uint16 period )
{
  gapCentralRoleRssi_t  *pRssi;

  // Verify link is up
  if (!linkDB_Up(connHandle))
  {
    return bleIncorrectMode;
  }

  // If already allocated
  if ((pRssi = gapCentralRole_RssiFind( connHandle )) != NULL)
  {
    // Stop timer
    osal_CbTimerStop( pRssi->timerId );
  }
  // Allocate structure
  else if ((pRssi = gapCentralRole_RssiAlloc( connHandle )) != NULL)
  {
    pRssi->period = period;
  }
  // Allocate failed
  else
  {
    return bleNoResources;
  }

  // Start timer
  osal_CbTimerStart( gapCentralRole_timerCB, (uint8 *) pRssi,
                     period, &pRssi->timerId );

  return SUCCESS;
}

/**
 * @brief   Cancel periodic RSSI reads on a link.
 *
 * Public function defined in central.h.
 */
bStatus_t GAPCentralRole_CancelRssi(uint16 connHandle )
{
  gapCentralRoleRssi_t  *pRssi;

  if ((pRssi = gapCentralRole_RssiFind( connHandle )) != NULL)
  {
    // Stop timer
    osal_CbTimerStop( pRssi->timerId );

    // Free RSSI structure
    gapCentralRole_RssiFree( connHandle );

    return SUCCESS;
  }

  // Not found
  return bleIncorrectMode;
}

/**
 * @brief   Central Profile Task initialization function.
 *
 * @param   taskId - Task ID.
 *
 * @return  void
 */
void GAPCentralRole_Init( uint8 taskId )
{
  uint8 i;

  gapCentralRoleTaskId = taskId;

  // Initialize internal data
  for ( i = 0; i < GAPCENTRALROLE_NUM_RSSI_LINKS; i++ )
  {
    gapCentralRoleRssi[i].connHandle = GAP_CONNHANDLE_ALL;
    gapCentralRoleRssi[i].timerId = INVALID_TIMER_ID;
  }

  // Initialize parameters

  // Retore items from NV
//  VOID osal_snv_read( BLE_NVID_IRK, KEYLEN, gapCentralRoleIRK );
//  VOID osal_snv_read( BLE_NVID_CSRK, KEYLEN, gapCentralRoleSRK );
//  VOID osal_snv_read( BLE_NVID_SIGNCOUNTER, sizeof( uint32 ), &gapCentralRoleSignCounter );

  // Register for HCI messages (for RSSI)
  GAP_RegisterForHCIMsgs( taskId );
}

/**
 * @brief   Central Profile Task event processing function.
 *
 * @param   taskId - Task ID
 * @param   events - Events.
 *
 * @return  events not processed
 */
uint16 GAPCentralRole_ProcessEvent( uint8 taskId, uint16 events )
{
  if ( events & SYS_EVENT_MSG )
  {
    uint8 *pMsg;

    if ( (pMsg = osal_msg_receive( gapCentralRoleTaskId )) != NULL )
    {
      gapCentralRole_ProcessOSALMsg( (osal_event_hdr_t *) pMsg );

      // Release the OSAL message
      VOID osal_msg_deallocate( pMsg );
    }

    // return unprocessed events
    return (events ^ SYS_EVENT_MSG);
  }

  if ( events & GAP_EVENT_SIGN_COUNTER_CHANGED )
  {
    // Sign counter changed, save it to NV
  //  VOID osal_snv_write( BLE_NVID_SIGNCOUNTER, sizeof( uint32 ), &gapCentralRoleSignCounter );

    return ( events ^ GAP_EVENT_SIGN_COUNTER_CHANGED );
  }

  // Discard unknown events
  return 0;
}

/*********************************************************************
 * @fn      gapCentralRole_ProcessOSALMsg
 *
 * @brief   Process an incoming task message.
 *
 * @param   pMsg - message to process
 *
 * @return  none
 */
static void gapCentralRole_ProcessOSALMsg( osal_event_hdr_t *pMsg )
{
  switch ( pMsg->event )
  {
    case HCI_GAP_EVENT_EVENT:
      if ( pMsg->status == HCI_COMMAND_COMPLETE_EVENT_CODE )
      {
        hciEvt_CmdComplete_t *pPkt = (hciEvt_CmdComplete_t *) pMsg;

        if ( pPkt->cmdOpcode == HCI_READ_RSSI )
        {
          uint16 connHandle = BUILD_UINT16( pPkt->pReturnParam[1], pPkt->pReturnParam[2] );
          int8 rssi = (int8) pPkt->pReturnParam[3];

          // Report RSSI to app
          if ( pGapCentralRoleCB && pGapCentralRoleCB->rssiCB )
          {
            pGapCentralRoleCB->rssiCB( connHandle, rssi );
          }
        }
      }
      break;

    case GAP_MSG_EVENT:
      gapCentralRole_ProcessGAPMsg( (gapEventHdr_t *) pMsg );
      break;

    case GAPCENTRALROLE_RSSI_MSG_EVT:
      {
        gapCentralRoleRssi_t *pRssi = ((gapCentralRoleRssiEvent_t *) pMsg)->pRssi;

        // If link is up and RSSI reads active
        if (pRssi->connHandle != GAP_CONNHANDLE_ALL &&
            linkDB_Up(pRssi->connHandle))
        {
          // Restart timer
          osal_CbTimerStart( gapCentralRole_timerCB, (uint8 *) pRssi,
                             pRssi->period, &pRssi->timerId );

          // Read RSSI
          VOID HCI_ReadRssiCmd( pRssi->connHandle );
        }
      }
      break;

    default:
      break;
  }
}

/*********************************************************************
 * @fn      gapCentralRole_ProcessGAPMsg
 *
 * @brief   Process an incoming task message from GAP.
 *
 * @param   pMsg - message to process
 *
 * @return  none
 */
static void gapCentralRole_ProcessGAPMsg( gapEventHdr_t *pMsg )
{
  switch ( pMsg->opcode )
  {
    case GAP_DEVICE_INIT_DONE_EVENT:
      {
        gapDeviceInitDoneEvent_t *pPkt = (gapDeviceInitDoneEvent_t *) pMsg;

        if ( pPkt->hdr.status == SUCCESS )
        {
          // Save off the generated keys
         // VOID osal_snv_write( BLE_NVID_IRK, KEYLEN, gapCentralRoleIRK );
         // VOID osal_snv_write( BLE_NVID_CSRK, KEYLEN, gapCentralRoleSRK );

          // Save off the information
          VOID osal_memcpy( gapCentralRoleBdAddr, pPkt->devAddr, B_ADDR_LEN );
        }
      }
      break;

    case GAP_LINK_ESTABLISHED_EVENT:
      {
        gapEstLinkReqEvent_t *pPkt = (gapEstLinkReqEvent_t *) pMsg;

        if (pPkt->hdr.status == SUCCESS)
        {
          // Notify the Bond Manager of the connection
          VOID GAPBondMgr_LinkEst( pPkt->devAddrType, pPkt->devAddr,
                                   pPkt->connectionHandle, GAP_PROFILE_CENTRAL );
        }
      }
      break;

    case GAP_LINK_TERMINATED_EVENT:
      {
        uint16 connHandle = ((gapTerminateLinkEvent_t *) pMsg)->connectionHandle;

        VOID GAPBondMgr_ProcessGAPMsg( (gapEventHdr_t *)pMsg );

        // Cancel RSSI reads
        GAPCentralRole_CancelRssi( connHandle );
      }
      break;

    // temporary workaround
    case GAP_SLAVE_REQUESTED_SECURITY_EVENT:
      VOID GAPBondMgr_ProcessGAPMsg( pMsg );
      break;

    default:
      break;
  }

  // Pass event to app
  if ( pGapCentralRoleCB && pGapCentralRoleCB->eventCB )
  {
    pGapCentralRoleCB->eventCB( (gapCentralRoleEvent_t *) pMsg );
  }
}

/*********************************************************************
 * @fn      gapCentralRole_RssiAlloc
 *
 * @brief   Allocate an RSSI structure.
 *
 * @param   connHandle - Connection handle
 *
 * @return  pointer to structure or NULL if allocation failed.
 */
static gapCentralRoleRssi_t *gapCentralRole_RssiAlloc( uint16 connHandle )
{
  uint8 i;

  // Find free RSSI structure
  for ( i = 0; i < GAPCENTRALROLE_NUM_RSSI_LINKS; i++ )
  {
    if ( gapCentralRoleRssi[i].connHandle == GAP_CONNHANDLE_ALL )
    {
      gapCentralRoleRssi[i].connHandle = connHandle;
      return &gapCentralRoleRssi[i];
    }
  }

  // No free structure found
  return NULL;
}

/*********************************************************************
 * @fn      gapCentralRole_RssiFind
 *
 * @brief   Find an RSSI structure.
 *
 * @param   connHandle - Connection handle
 *
 * @return  pointer to structure or NULL if not found.
 */
static gapCentralRoleRssi_t *gapCentralRole_RssiFind( uint16 connHandle )
{
  uint8 i;

  // Find free RSSI structure
  for ( i = 0; i < GAPCENTRALROLE_NUM_RSSI_LINKS; i++ )
  {
    if ( gapCentralRoleRssi[i].connHandle == connHandle )
    {
      return &gapCentralRoleRssi[i];
    }
  }

  // Not found
  return NULL;
}

/*********************************************************************
 * @fn      gapCentralRole_RssiFree
 *
 * @brief   Free an RSSI structure.
 *
 * @param   connHandle - Connection handle
 *
 * @return  none
 */
static void gapCentralRole_RssiFree( uint16 connHandle )
{
  uint8 i;

  // Find RSSI structure
  for ( i = 0; i < GAPCENTRALROLE_NUM_RSSI_LINKS; i++ )
  {
    if ( gapCentralRoleRssi[i].connHandle == connHandle )
    {
      gapCentralRoleRssi[i].connHandle = GAP_CONNHANDLE_ALL;
      break;
    }
  }
}

/*********************************************************************
 * @fn      gapCentralRole_timerCB
 *
 * @brief   OSAL timer callback function
 *
 * @param   pData - Data pointer
 *
 * @return  none
 */
static void gapCentralRole_timerCB( uint8 *pData )
{
  gapCentralRoleRssiEvent_t *pMsg;

  // Timer has expired so clear timer ID
  ((gapCentralRoleRssi_t *) pData)->timerId = INVALID_TIMER_ID;

  // Send OSAL message
  pMsg = (gapCentralRoleRssiEvent_t *) osal_msg_allocate( sizeof(gapCentralRoleRssiEvent_t) );
  if ( pMsg )
  {
    pMsg->hdr.event = GAPCENTRALROLE_RSSI_MSG_EVT;
    pMsg->pRssi = (gapCentralRoleRssi_t *) pData;

    osal_msg_send ( gapCentralRoleTaskId, (uint8 *) pMsg );
  }
}

/*********************************************************************
*********************************************************************/
